package com.come4loves.game.server.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.come4loves.core.comunicate.AllowAccess;
import com.come4loves.core.comunicate.protocol.DataPacket;
import com.come4loves.core.comunicate.router.Router;
import com.come4loves.core.context.SpringContext;
import com.come4loves.core.utils.HostUtil;
import com.come4loves.game.component.listener.ListenerFacade;
import com.come4loves.game.component.listener.impl.ListenerFacadeImpl;
import com.come4loves.game.server.firewall.Firewall;
import com.come4loves.game.server.router.GatewayRouterImpl;
import com.come4loves.game.server.session.ChannelHandlerContextManager;
import com.come4loves.game.server.session.PlayerContext;

public class GatewayHandler extends ChannelInboundHandlerAdapter  {

	protected Logger LOGGER = LoggerFactory.getLogger(getClass());

	ListenerFacade listenerFacade = (ListenerFacade) SpringContext.getBean(ListenerFacadeImpl.class);

	public GatewayHandler() {
		super();
	}
	
	@Override
    public void handlerAdded(ChannelHandlerContext ctx) {
		AllowAccess access = (AllowAccess) SpringContext.getBean(AllowAccesGamePortImpl.class) ;
		String ip = HostUtil.getRomoteIP(ctx);
		if (!access.allowIp(ip)) {
			LOGGER.debug("not allow ip access, ip:{}", ip);
			ctx.close();
			return;
		}
		
		Firewall firewall = (Firewall) SpringContext.getBean(Firewall.class) ;
		PlayerContext playerContext = (PlayerContext) SpringContext.getBean(PlayerContext.class);
		if (firewall.isBlocked(ctx)) {
			Long actorId = playerContext.getActorId(ctx);
			LOGGER.info(String.format("firewall is blocked actorId:[%s] sessionId:[%s]", actorId, ctx.attr(ChannelHandlerContextManager.CONTEXT_INDEX_KEY).get()));
			ctx.close();
			return;
		}
		int currClients = firewall.increaseClients();
		if (firewall.isMaxClientLimit(currClients)) {
			LOGGER.info("Connections limit, close session...");
			ctx.close();
			return;
		}
		ctx.attr(ChannelHandlerContextManager.CONTEXT_INDEX_KEY).set(currClients);
		playerContext.put2AnonymousList(ctx);
		LOGGER.debug(String.format("Session add, sessionId:[%s], iplocal:[%s], ipremote:[%s]", playerContext.getAtomicId(ctx), ctx.channel().localAddress(), ctx.channel().remoteAddress()));
		
    }
	
	 @Override
	    public void handlerRemoved(ChannelHandlerContext ctx) {
	    	Firewall firewall = (Firewall) SpringContext.getBean(Firewall.class) ;
	    	firewall.decreaseClients();
	    	firewall.removeBlockCounter(ctx);
	    	PlayerContext playerContext = (PlayerContext) SpringContext.getBean(PlayerContext.class);
	    	LOGGER.debug(String.format("Session removed, sessionId:[%s]", playerContext.getAtomicId(ctx)));
	    	Long actorId = playerContext.getActorId(ctx);
	    	if (actorId != null && actorId != 0) {
	    		playerContext.removeOnlineList(actorId);
	    		listenerFacade.addLogoutListener(actorId);
	    	} else {
	    		playerContext.removeFromAnonymousList(ctx);
	    	}
	    }
	

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		Firewall firewall = (Firewall) SpringContext.getBean(Firewall.class) ;
    	PlayerContext playerContext = (PlayerContext) SpringContext.getBean(PlayerContext.class);
    	//如果开始防火墙，则进行过滤
		if (firewall.isEnableFirewall()) {
			
			//如果sesion没有分配唯一id,或者已经超过限制。则关闭连接
			Integer currClients = playerContext.getAtomicId(ctx);
			if (currClients == null || firewall.isMaxClientLimit(currClients)) {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug(String.format("firewall is maxConnection. curClients:[%s] sessionId:[%s]", currClients, currClients));
				}
				playerContext.close(ctx);
				return;
			}
			
			boolean blocked = firewall.isBlocked(ctx);
			// 包大小监控
			if (blocked == false && (msg instanceof DataPacket)) {
				DataPacket request = (DataPacket) msg;
				int length = request.getValue() == null ? 0 : request.getValue().length;
				blocked = firewall.blockedByBytes(ctx, length);
			}

			// 包数量监控
			if (blocked == false) {
				blocked = firewall.blockedByPacks(ctx, 1);
			}

			// 封号
			if (blocked) {
				String remoteIp = playerContext.getRemoteIp(ctx);
				long actorId = playerContext.getActorId(ctx);
				LOGGER.warn(String.format("In blacklist: [ip: %s, actorId: %d] sessionId:[%s]", remoteIp, actorId, currClients));
				playerContext.close(ctx);
				return;
			}
		}
		

		Router router = (Router) SpringContext.getBean(GatewayRouterImpl.class);
		DataPacket request = (DataPacket) msg;
		Long actorId = playerContext.getActorId(ctx);

		if (LOGGER.isDebugEnabled()) {
			int len = request.getValue() != null ? request.getValue().length : 0;
			LOGGER.debug(String.format("[channelRead] sessionId:[%s] actorId:[%d] module:[%d] cmd:[%d], length:[%s bytes]", ctx.attr(ChannelHandlerContextManager.CONTEXT_INDEX_KEY).get(), actorId,
					request.getModule(), request.getCmd(), len));
			long startTime = System.currentTimeMillis();
			router.forward(ctx, request);// 分派到各模块的方法
			long endTime = System.currentTimeMillis();
			LOGGER.debug(String.format("request consume time:[%s]ms", endTime - startTime));
		} else {
			router.forward(ctx, request);// 分派到各模块的方法
		}

	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		PlayerContext playerContext = (PlayerContext) SpringContext.getBean(PlayerContext.class);
		playerContext.close(ctx);
		Throwable ex = cause;
		StringBuilder builder = new StringBuilder();
		while (ex != null) {
			StackTraceElement[] stackTrace = ex.getStackTrace();
			for (StackTraceElement st : stackTrace) {
				builder.append("\t").append(st.toString()).append("\n");
			}
			if (ex == ex.getCause()) {
				break;
			} else {
				ex = ex.getCause();
				if (ex != null) {
					builder.append("CAUSE\n").append(ex.getMessage()).append(ex).append("\n");
				}
			}
		}
		long actorId = playerContext.getActorId(ctx);
		String className = cause.getClass().getName();
		// filter connection reset by peer exception ...
		if (cause instanceof IOException && builder.indexOf("sun.nio.ch.SocketChannelImpl.read") > 0) {
			LOGGER.error(String.format("[exceptionCaught] actorId:[%s] className:[%s] msg:[%s]", actorId, className, cause.getMessage()));
			return;
		}

		LOGGER.error(String.format("[exceptionCaught] actorId:[%s] error:[%s]", actorId, builder.toString()));
	}
	
    
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
    		throws Exception {
    	if(IdleStateEvent.class.isAssignableFrom(evt.getClass())){
    		PlayerContext playerContext = (PlayerContext) SpringContext.getBean(PlayerContext.class);
    		long actorId = playerContext.getActorId(ctx);
    		LOGGER.error(String.format("[userEventTriggered] actorId[%s], seesionId:[%s].", actorId,playerContext.getAtomicId(ctx)));
			playerContext.close(ctx);
		}
		
    }


}
